package gui;


import java.awt.Color;
import java.util.Hashtable;

import javax.swing.event.DocumentEvent;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

public class CMOMDocument extends DefaultStyledDocument {
 
	private static final long serialVersionUID = 1L;
	
	private DefaultStyledDocument documento;
	
	private Element rootElement;
	
	private MutableAttributeSet normal;
	private MutableAttributeSet cefanys;
	private MutableAttributeSet numero;
	private MutableAttributeSet palavraReservada;
	
	private Hashtable<String, Object> palavrasReservadas;
	private Hashtable<String, Object> simbolosReservados;
	
	private static String[] PALAVRAS_RESERVADAS = 
		{"inteiro", 
		"logico", 
		"verdadeiro", 
		"falso", 
		"funcao", 
		"executar", 
		"se", 
		"senao", 
		"enquanto", 
		"retornar", 
		"pare", 
		"continue", 
		"imprimir",
		"nada"};
	
	private static String[] SIMBOLOS_RESERVADOS = {"{", "}", 
		"+", "-", 
		"*", "/", 
		"==", "!=", "=", 
		">", ">=", 
		"<", "<=", 
		"(", ")", 
		";", ",",
		"{}", "()"};
 
	public CMOMDocument() {

		documento = this;
		rootElement = documento.getDefaultRootElement();
		putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
		
		normal = new SimpleAttributeSet();
		StyleConstants.setForeground(normal, Color.WHITE);

		cefanys = new SimpleAttributeSet();
		StyleConstants.setForeground(cefanys, Color.YELLOW);
		StyleConstants.setBold(cefanys, true);

		numero = new SimpleAttributeSet();
		Color blue = new Color(0, 200, 0);
		StyleConstants.setForeground(numero, blue);
		StyleConstants.setBold(numero, true);
		
		palavraReservada = new SimpleAttributeSet();
		Color red = new Color(250, 0, 100);
		StyleConstants.setForeground(palavraReservada, red);
		StyleConstants.setBold(palavraReservada, true);
		
		palavrasReservadas = new Hashtable<String, Object>();
		palavrasReservadas =preenchePalavrasReservadas();
		
		simbolosReservados = new Hashtable<String, Object>();
		simbolosReservados = preencheSimbolosReservados();
	}
	
	private Hashtable<String, Object> preenchePalavrasReservadas(){
		Hashtable<String, Object> palavrasReservadas = new Hashtable<String, Object>();
		Object objetoModelo = new Object();
		for (String palavra : PALAVRAS_RESERVADAS) {
			palavrasReservadas.put(palavra, objetoModelo);
		}
		return palavrasReservadas;		
	}
	
	private Hashtable<String, Object> preencheSimbolosReservados(){
		Hashtable<String, Object> palavrasReservadas = new Hashtable<String, Object>();
		Object objetoModelo = new Object();
		for (String palavra : SIMBOLOS_RESERVADOS) {
			palavrasReservadas.put(palavra, objetoModelo);
		}
		return palavrasReservadas;		
	}
	
	private boolean isSimboloReservado(String palavra){
		Object objeto = simbolosReservados.get(palavra);
		return objeto == null ? false : true;
	}
	
	private boolean isNumero(String palavra){		
		return palavra.matches("[0-9]+");
	}
 
	/*
	 * Override to apply syntax highlighting after the document has been updated
	 */
	public void insertString(int offset, String str, AttributeSet a)
			throws BadLocationException {
		super.insertString(offset, str, a);
		processChangedLines(offset, str.length());
	}
 
	/*
	 * Override to apply syntax highlighting after the document has been updated
	 */
	public void remove(int offset, int length) throws BadLocationException {
		super.remove(offset, length);
		processChangedLines(offset, 0);
	}
 
	/*
	 * Determine how many lines have been changed,
	 * then apply highlighting to each line
	 */
	private void processChangedLines(int offset, int length)
			throws BadLocationException {
		String content = documento.getText(0, documento.getLength());
		// The lines affected by the latest document update
		int startLine = rootElement.getElementIndex(offset);
		int endLine = rootElement.getElementIndex(offset + length);
		// Make sure all comment lines prior to the start line are commented
		// and determine if the start line is still in a multi line comment
		// Do the actual highlighting
		for (int i = startLine; i <= endLine; i++)
			applyHighlighting(content, i);
	}
 
	/*
	 * Parse the line to determine the appropriate highlighting
	 */
	private void applyHighlighting(String content, int line)
			throws BadLocationException {
		int startOffset = rootElement.getElement(line).getStartOffset();
		int endOffset = rootElement.getElement(line).getEndOffset() - 1;
		int lineLength = endOffset - startOffset;
		int contentLength = content.length();
		if (endOffset >= contentLength)
			endOffset = contentLength - 1;
		// set normal attributes for the line
		documento.setCharacterAttributes(startOffset, lineLength, normal, true);
		// check for tokens
		checkForTokens(content, startOffset, endOffset);
	}
 
	/*
	 * Parse the line for tokens to highlight
	 */
	private void checkForTokens(String content, int startOffset, int endOffset) {
		while (startOffset <= endOffset) {
			// skip the delimiters to find the start of a new token
			while (isDelimiter(content.substring(startOffset, startOffset + 1))) {
				if (startOffset < endOffset)
					startOffset++;
				else
					return;
			}
			// Extract and process the entire token
				startOffset = getOtherToken(content, startOffset, endOffset);
		}
	}
 
	private int getOtherToken(String content, int startOffset, int endOffset) {
		int endOfToken = startOffset + 1;
		while (endOfToken <= endOffset) {
			if (isDelimiter(content.substring(endOfToken, endOfToken + 1)))
				break;
			endOfToken++;
		}
		String token = content.substring(startOffset, endOfToken);
		if(token.equalsIgnoreCase("cefanys")){
			documento.setCharacterAttributes(startOffset, endOfToken - startOffset,
					cefanys, false);
		} else if (isKeyword(token))
			documento.setCharacterAttributes(startOffset, endOfToken - startOffset,
					palavraReservada, false);
		else if(isNumero(token)){
			documento.setCharacterAttributes(startOffset, endOfToken - startOffset,
					numero, false);
		}
		return endOfToken + 1;
	}
 
	/*
	 * This updates the colored text and prepares for undo event
	 */
	protected void fireInsertUpdate(DocumentEvent evt) {
 
		super.fireInsertUpdate(evt);
 
		try {
			processChangedLines(evt.getOffset(), evt.getLength());
		} catch (BadLocationException ex) {
			System.out.println("" + ex);
		}
	}
 
	/*
	 * This updates the colored text and does the undo operation
	 */
	protected void fireRemoveUpdate(DocumentEvent evt) {
 
		super.fireRemoveUpdate(evt);
 
		try {
			processChangedLines(evt.getOffset(), evt.getLength());
		} catch (BadLocationException ex) {
			System.out.println("" + ex);
		}
	}
 
	/*
	 * Override for other languages
	 */
	protected boolean isDelimiter(String character) {
		if (Character.isWhitespace(character.charAt(0))
				|| isSimboloReservado(character))
			return true;
		else
			return false;
	}
 
	/*
	 * Override for other languages
	 */
	protected boolean isKeyword(String token) {
		Object o = palavrasReservadas.get(token);
		return o == null ? false : true;
	}
 
}